/**
 * @file uniform_distr.h
 *
 * Uniform distribution classes
 * 
 * @author Dahua Lin
 */

#ifndef SLIPP_UNIFORM_DISTR_H_
#define SLIPP_UNIFORM_DISTR_H_

#include <slipp/prob_base/pdistr_base.h>
#include <limits>

namespace sli
{

	template<class RStream>
	class uniform_sampler
	{
	public:
		SLIPP_DEFINE_SCALAR_DISTR_TYPES(double)

	public:
		uniform_sampler(RStream& rstream, const scalar_type& a, const scalar_type& span)
		: m_rstream(rstream), m_a(a), m_span(span)
		{
		}

		sample_type draw() const
		{
			return m_rstream.rand_f64() * m_span + m_a;
		}

		sample_collection_type draw(size_t n) const
		{
			sample_collection_type u((index_t)n);
			m_rstream.rand_f64_m(n, u.data());
			return u * m_span + m_a;
		}

	private:
		RStream& m_rstream;
		scalar_type m_a;
		scalar_type m_span;

	}; // end class uniform_sampler


	/**
	 * A real-valued uniform distribution
	 */
	class uniform_distr
	{
	public:
		SLIPP_DEFINE_SCALAR_DISTR_TYPES(double)

		class param_type
		{
		public:
			param_type() : m_a(0), m_b(1) { }

			param_type(const scalar_type& a, const scalar_type& b)
			: m_a(a), m_b(b) { }

			scalar_type a() const { return m_a; }
			scalar_type b() const { return m_b; }
			scalar_type span() const { return m_b - m_a; }

			bool operator == (const param_type& rhs) const
			{
				return m_a == rhs.m_a && m_b == rhs.m_b;
			}

			bool operator != (const param_type& rhs) const
			{
				return !(operator == (rhs));
			}

		private:
			scalar_type m_a;
			scalar_type m_b;
		};

	public:
		// constructor

		uniform_distr()
		: m_param(), m_span(m_param.span())
		{
		}

		uniform_distr(const scalar_type& a, const scalar_type& b)
		: m_param(a, b), m_span(m_param.span())
		{
		}

		uniform_distr(const param_type& param)
		: m_param(param), m_span(m_param.span())
		{
		}

		// parameters

		const param_type& param() const
		{
			return m_param;
		}

		scalar_type a() const { return m_param.a(); }

		scalar_type b() const { return m_param.b(); }

		scalar_type span() const { return m_span; }

		// statistics

		double mean() const
		{
			return (a() + b()) * 0.5;
		}

		double var() const
		{
			return math::sqr(span()) * (1.0/12);
		}

		double entropy() const
		{
			return math::log(span());
		}

	public:

		class pdf_evaluator_type
		{
		public:
			typedef uniform_distr::sample_type sample_type;
			typedef uniform_distr::sample_collection_type sample_collection_type;

			pdf_evaluator_type(const scalar_type& a, const scalar_type& b, const scalar_type& span)
			: m_a(a), m_b(b)
			, m_p(math::rcp(span))
			, m_logp(-math::log(span))
			{
			}

			SLIPP_ENSURE_INLINE double pdf(const sample_type& x) const
			{
				return in_range(x) ? m_p : 0.0;
			}

			SLIPP_ENSURE_INLINE double logpdf(const sample_type& x) const
			{
				return in_range(x) ? m_logp : -std::numeric_limits<double>::infinity();
			}

			darow_t pdf(const sample_collection_type& X) const
			{
				return map(_detail::make_pdf_eval_functor(*this), X);
			}

			darow_t logpdf(const sample_collection_type& X) const
			{
				return map(_detail::make_logpdf_eval_functor(*this), X);
			}

		private:
			SLIPP_ENSURE_INLINE bool in_range(const scalar_type& x) const
			{
				return x >= m_a && x <= m_b;
			}

		private:
			scalar_type m_a;
			scalar_type m_b;
			double m_p;
			double m_logp;
		};

		pdf_evaluator_type get_pdf_evaluator() const
		{
			return pdf_evaluator_type(a(), b(), span());
		}

	public:
		// sampling

		template<class RStream>
		uniform_sampler<RStream> get_sampler(RStream& rstream) const
		{
			return uniform_sampler<RStream>(rstream, a(), span());
		}


	private:
		param_type m_param;
		scalar_type m_span;
	};

	// traits

	SLIPP_DEFINE_BASIC_DISTR_TRAITS(uniform_distr, uniform_sampler)

}

#endif 
